import { reactive, inject } from 'vue'

const storeKey = 'store'

class Store {
  constructor(options) {
    const store = this
    store._state = reactive({data: options.state})
    registerGetters(store, options.getters)
    registerMutations(store, options.mutations)
    registerActions(store, options.actions)
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  get state() {
    return this._state.data
  }

  get getters() {
    return this._getters
  }

  commit (type, payload) {
    const mutation = this._mutations[type];
    if (mutation) {
      mutation(this.state, payload);
    }
  }

  dispatch (type, payload) {
    const action = this._actions[type];
    if (action) {
      return action(this, payload);
    }
  }

  install(app, injectKey = storeKey) {
    app.provide(injectKey, this)
    app.config.globalProperties.$store = this
  }
}

function registerGetters(store, getters) {
  if (!getters) return;

  store._getters = {}
  forEachValue(getters, (getter, key) => {
    Object.defineProperty(store._getters, key, {
      get: () => getter(store.state),
    });
  });
}

function registerMutations(store, mutations) {
  if (!mutations) return;

  store._mutations = Object.create(null)
  forEachValue(mutations, (mutation, key) => {
    store._mutations[key] = (state, payload) => mutation(state, payload);
  });
}

function registerActions(store, actions) {
  if (!actions) return;

  store._actions = Object.create(null)
  forEachValue(actions, (action, key) => {
    store._actions[key] = (store, payload) => action(store, payload);
  });
}

function forEachValue(obj, fn) {
    Object.keys(obj).forEach(key => fn(obj[key], key));
}

function createStore(options) {
  return new Store(options)
}

function useStore(injectKey = storeKey) {
    const store = inject(injectKey);
    if (!store) {
        throw new Error('No store provided.');
    }
    return store;
}

export {
  createStore,
  useStore
}

